/*******************************************************************************
 * Copyright 2020 huanggefan.cn
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package core

import (
	"bytes"
	"encoding/json"
	"errors"
	"time"

	"google.golang.org/grpc/metadata"
)

/*
Token格式：
[TokenHeader] [Signature] [TokenSplitCharacter] [Base64_JSON]

TokenHeader:            Token头，长度8字符
Signature:              Token Sha1签名，长度40字符
TokenSplitCharacter:    Token分割字符，长度1字符
Base64_JSON:            Token字符串
*/

type Token struct {
	Timestamp int64  `json:"timestamp"`
	LifeCycle int64  `json:"lifeCycle"`
	AccountID int32 `json:"accountID"`
	Name      string `json:"name"`
	Gender    int32  `json:"gender"`
	Type      int32  `json:"type"`
	IsManager bool   `json:"isManager"`
}

const tokenHeaderLen = len(TokenHeader)
const tokenSignLen = 40
const tokenSplitCharacterLen = 1

func NewToken() *Token {
	return &Token{
		Timestamp: time.Now().Unix(),
		LifeCycle: TokenTimeout,
	}
}

func (token *Token) PackToken(tokenCert *string) string {
	token.Timestamp = time.Now().Unix() // 获取token下发时间戳
	token.LifeCycle = TokenTimeout

	infoBytes, _ := json.Marshal(token) // json编码token信息
	infoStr := string(infoBytes)

	infoBase64 := StringToBase64(infoStr) // base编码json token信息

	infoSignature := signToken(infoBase64, tokenCert) // 签名token

	// 生成token
	var buffer bytes.Buffer
	buffer.WriteString(TokenHeader)
	buffer.WriteString(infoSignature)
	buffer.WriteString(TokenSplitCharacter)
	buffer.WriteString(infoBase64)

	return buffer.String()
}

func UnpackTokenFromMetadata(md metadata.MD) (*Token, error) {
	token := NewToken()
	tokenString, err := ReadTokenFromMetadata(md)
	if err != nil {
		return nil, err
	}
	err = token.UnpackToken(tokenString, &FlagTokenCert)
	if err != nil {
		return nil, err
	}
	return token, nil
}

func (token *Token) UnpackToken(v string, tokenCert *string) error {
	err := VerifyToken(v, tokenCert)
	if err != nil {
		return err
	}

	infoBase64 := v[tokenHeaderLen+tokenSignLen+tokenSplitCharacterLen:] // 提取base64字符串
	infoStr, _ := Base64ToString(infoBase64)                             // base64解码

	_ = json.Unmarshal([]byte(infoStr), token) // json解码

	tokenExpirationDate := token.Timestamp + token.LifeCycle // 检验token是否过期
	if time.Now().Unix() >= tokenExpirationDate {
		return errors.New("token timeout")
	}

	return nil
}

func VerifyToken(v string, tokenCert *string) error {
	if len(v) <= (tokenHeaderLen + tokenSignLen + tokenSplitCharacterLen) { // token长度校验
		return errors.New("token is too sort")
	}

	tokenHeader := v[0:tokenHeaderLen]
	if tokenHeader != TokenHeader { // token头校验
		return errors.New("token header not pass")
	}

	tokenSplitCharacter := string(v[tokenHeaderLen+tokenSignLen])
	if tokenSplitCharacter != TokenSplitCharacter { // token分割字符校验
		return errors.New("token split character not pass")
	}

	tokenInfo := v[tokenHeaderLen+tokenSignLen+tokenSplitCharacterLen:]
	tokenSign := v[tokenHeaderLen : tokenHeaderLen+tokenSignLen]
	if signToken(tokenInfo, tokenCert) != tokenSign { // token签名校验
		return errors.New("token sign not pass")
	}

	return nil
}

func signToken(infoBase64 string, tokenCert *string) string {
	var buffer bytes.Buffer
	buffer.WriteString(TokenHeader)
	buffer.WriteString(infoBase64)

	if tokenCert == nil {
		buffer.WriteString(DefTokenCert)
	} else {
		buffer.WriteString(*tokenCert)
	}

	buffer.WriteString(TokenSplitCharacter)

	return StringToSha1(buffer.String())
}

func ReadTokenFromMetadata(md metadata.MD) (string, error) {
	cookies := md.Get("Authorization")
	if len(cookies) == 0 {
		return "", errors.New("no Authorization header")
	}
	return cookies[0], nil
}

func NewTokenCert(str string) *string {
	return &str
}
